﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace SmartVault.Core
{
    public interface ISVException
    {
        int ErrorCode { get; }
        string Message { get; }
        string CorrectiveLink { get; }
        string InstanceGuid { get; }
        int HttpStatusCode { get; }
    }

    /// <summary> Represents the root SmartVault exception </summary>
    [Serializable]
    public class SVException : ApplicationException, ISVException
    {
        /// <summary> Constructor with message </summary>
        internal SVException(string message) : base(message) { }
        /// <summary> Constructor with message an inner exception </summary>
        internal SVException(string message, Exception innerException)
            : base(message, innerException)
        {
            string guid = null;
            while (innerException != null)
            {
                if (innerException.Data.Contains("InstanceGuid"))
                {
                    guid = innerException.Data["InstanceGuid"] as string;
                    break;
                }
                innerException = innerException.InnerException;
            }
            base.Data["InstanceGuid"] = guid ?? Guid.NewGuid().ToString();
        }

        /// <summary> Serialization constructor </summary>
        internal SVException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }

        /// <summary> Gets or sets the exception instance identifier used to track exceptions across process boundries </summary>
        public string InstanceGuid
        {
            get { return Data["InstanceGuid"] as string; }
            set { Data["InstanceGuid"] = value; }
        }

        /// <summary> Gets or sets the link for users to take corrective action or null if none exists </summary>
        public string CorrectiveLink
        {
            get { return Data["CorrectiveLink"] as string; }
            set { Data["CorrectiveLink"] = value; }
        }

        /// <summary> Gets the HTTP status code used for this exception type </summary>
        public int HttpStatusCode
        {
            get
            {
                object data = Data["HttpStatusCode"];

                int value = data == null ? 500 : (int)data;

                return value;
            }
            set
            {
                Data["HttpStatusCode"] = value;
            }
        }

        /// <summary> Gets the HResult for this exception type </summary>
        public int ErrorCode { get { return HResult; } }

        /// <summary> Creates and returns a string representation of the current exception. </summary>
        public override string ToString()
        {
            return String.Format("{0}\r\nError Code: 0x{1:x8}\r\nException Guid: {2}", base.ToString(), HResult, InstanceGuid);
        }

        delegate SVException CreateOperation(string msg);
        static readonly Dictionary<int, CreateOperation> FacilityLookup;

        protected SVException(int hResult, string message)
            : this(message)
        {
            this.HResult = hResult;
        }

        static SVException()
        {
            FacilityLookup = new Dictionary<int, CreateOperation>();
            foreach (Type t in typeof(SVException).Assembly.GetTypes())
            {
                PropertyInfo i = t.GetProperty("StaticErrorCode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetField);
                if (i == null)
                    continue;
                Delegate fn = Delegate.CreateDelegate(typeof(CreateOperation), t, "Create", false, false);
                if (fn != null)
                    FacilityLookup.Add((int)i.GetValue(null, null), (CreateOperation)fn);
            }
        }

        /// <summary>
        /// Creates an excpetion from the provided hresult and message text
        /// </summary>
        public static SVException CreateFromHResult(int hResult, string message)
        {
            CreateOperation op = null;

            if (FacilityLookup.TryGetValue(hResult, out op))
            {
                return op(message);
            }
            else
            {
                return SV_GeneralFailure.Create(message);
            }
        }

        public static bool IsWellKnownException(int hResult)
        {
            return FacilityLookup.ContainsKey(hResult);
        }
    }
}
